package com.sheng.project.study.springaop;

import cn.hutool.core.util.ObjectUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.springframework.aop.*;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.lang.NonNull;

import java.lang.reflect.Method;

/**
 * @author liusheng
 * @date 2023/06/30
 */
public class UserServiceTest {

    @Test
    public void test1() {
        // 在 UserService.work() 方法执行之前，打印一句：你好：userName
        UserService target = new UserService();
        Pointcut pointcut = new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                // 判断是否是 UserService 类型
                return UserService.class::isAssignableFrom;
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new MethodMatcher() {
                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass) {
                        // 判断方法名是否符合要求
                        return "work".equals(method.getName());
                    }

                    @Override
                    public boolean isRuntime() {
                        return false;
                    }

                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass, @NonNull Object... args) {
                        return false;
                    }
                };
            }
        };

        // 在方法执行之前打印，那么需要创建 MethodBeforeAdvice 对象
        MethodBeforeAdvice methodBeforeAdvice = (method, args, target1) -> {
            System.out.println("你好：" + args[0]);
        };
        // 创建 Advisor 将 pointcut 和 Advice 组合起来
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, methodBeforeAdvice);

        // 创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 放入目标对象
        proxyFactory.setTarget(target);
        // 为目标增强功能
        proxyFactory.addAdvisor(defaultPointcutAdvisor);
        // 获取代理对象
        UserService proxy = (UserService) proxyFactory.getProxy();
        proxy.work("黄金鹏");
    }

    @Test
    public void test2() {
        // 统计一下 work 方法的耗时，将耗时输出
        UserService target = new UserService();
        Pointcut pointcut = new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                return UserService.class::isAssignableFrom;
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new MethodMatcher() {
                    @Override
                    public boolean matches(Method method, Class<?> targetClass) {
                        return "work".equals(method.getName());
                    }

                    @Override
                    public boolean isRuntime() {
                        return false;
                    }

                    @Override
                    public boolean matches(Method method, Class<?> targetClass, Object... args) {
                        return false;
                    }
                };
            }
        };

        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                Method method = invocation.getMethod();
                long start = System.nanoTime();
                Object proceed = invocation.proceed();
                long end = System.nanoTime();
                System.out.println("方法 " + method.getDeclaringClass() + "." + method.getName() + "() 执行耗时：" + (end - start));
                return proceed;
            }
        };
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, methodInterceptor);
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvisor(defaultPointcutAdvisor);

        UserService proxy = (UserService) proxyFactory.getProxy();
        proxy.work("黄金鹏");
    }

    @Test
    public void test3() {
        // userName 中包含“粉丝”关键字，输出一句：感谢您一路的支持
        UserService target = new UserService();
        Pointcut pointcut = new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                // 判断是否是 UserService 类型
                return UserService.class::isAssignableFrom;
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new MethodMatcher() {
                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass) {
                        // 判断方法名是否符合要求
                        return "work".equals(method.getName());
                    }

                    @Override
                    public boolean isRuntime() {
                        // 注意这个地方要返回 true
                        return true;
                    }

                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass, @NonNull Object... args) {
                        if (ObjectUtil.isNotEmpty(args) && args.length == 1) {
                            String arg = (String) args[0];
                            return arg.contains("粉丝");
                        }
                        return false;
                    }
                };
            }
        };

        // 在方法执行之前打印，那么需要创建 MethodBeforeAdvice 对象
        MethodBeforeAdvice methodBeforeAdvice = (method, args, target1) -> {
            System.out.println("感谢您一路的支持");
        };
        // 创建 Advisor 将 pointcut 和 Advice 组合起来
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, methodBeforeAdvice);

        // 创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 放入目标对象
        proxyFactory.setTarget(target);
        // 为目标增强功能
        proxyFactory.addAdvisor(defaultPointcutAdvisor);
        // 获取代理对象
        UserService proxy = (UserService) proxyFactory.getProxy();
        proxy.work("黄金鹏");
        proxy.work("粉丝");
    }

    @Test
    public void test4() {
        // work 方法执行之后，打印一句：再见：userName
        UserService target = new UserService();
        Pointcut pointcut = new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                // 判断是否是 UserService 类型
                return UserService.class::isAssignableFrom;
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new MethodMatcher() {
                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass) {
                        // 判断方法名是否符合要求
                        return "work".equals(method.getName());
                    }

                    @Override
                    public boolean isRuntime() {
                        return false;
                    }

                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass, @NonNull Object... args) {
                        return false;
                    }
                };
            }
        };

        // 在方法执行之后打印，那么需要创建 AfterReturningAdvice 对象
        AfterReturningAdvice afterReturningAdvice = (returnValue, method, args, target12) -> {
            System.out.println("再见：" + args[0]);
        };
        // 创建 Advisor 将 pointcut 和 Advice 组合起来
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, afterReturningAdvice);

        // 创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 放入目标对象
        proxyFactory.setTarget(target);
        // 为目标增强功能
        proxyFactory.addAdvisor(defaultPointcutAdvisor);
        // 获取代理对象
        UserService proxy = (UserService) proxyFactory.getProxy();
        proxy.work("黄金鹏");
    }

    @Test
    public void test5() {
        // 在 work 方法中抛出一个异常，然后通过 aop 中的 ThrowsAdvice 类型的通知来拦截这个异常信息，然后将异常错误信息打印出来
        UserService target = new UserService();
        Pointcut pointcut = new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                // 判断是否是 UserService 类型
                return UserService.class::isAssignableFrom;
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new MethodMatcher() {
                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass) {
                        // 判断方法名是否符合要求
                        return "work".equals(method.getName());
                    }

                    @Override
                    public boolean isRuntime() {
                        return false;
                    }

                    @Override
                    public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass, @NonNull Object... args) {
                        return false;
                    }
                };
            }
        };
        // 创建 Advisor 将 pointcut 和 Advice 组合起来
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, new SendMsgThrowsAdvice());

        // 创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 放入目标对象
        proxyFactory.setTarget(target);
        // 为目标增强功能
        proxyFactory.addAdvisor(defaultPointcutAdvisor);
        // 获取代理对象
        UserService proxy = (UserService) proxyFactory.getProxy();
        proxy.work("黄金鹏");
    }

    public static class SendMsgThrowsAdvice implements ThrowsAdvice {
        //注意方法名称必须为afterThrowing
        public void afterThrowing(Method method, Object[] args, Object target, RuntimeException ex) {
            //监控到异常后发送消息通知开发者
            System.out.println("***");
            System.out.println("IllegalArgumentException Capture");
            System.out.println("Caught: " + ex.getClass().getName());
            System.out.println("Method: " + method.getName());
            System.out.println("***\n");
        }
    }
}
